﻿/* 
   Copyright 2011 LUCA Studios LLC & John Evans

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

namespace LUCAStudios.Silverlight.Controls
{
    using System;
    using System.Linq;
    using System.Windows;
    using System.Windows.Media;

    /// <summary>
    /// Provides various FrameworkElement/VisualTree helper extension methods
    /// </summary>
    public static class SilverlightHelpers
    {
        private static readonly Point DefaultPoint = new Point(0, 0);

        /// <summary>
        /// Transforms a Framework Element position by the given x,y amount.
        /// </summary>
        /// <param name="that"></param>
        /// <param name="position"></param>
        public static void RelativeTransformPositionTo(this FrameworkElement that, Point position)
        {
            var tg = that.RenderTransform as TransformGroup;
            if (tg == null)
            {
                tg = new TransformGroup();
                tg.Children.Add(that.RenderTransform);
            }
            var t = new TranslateTransform { X = position.X, Y = position.Y };
            tg.Children.Add(t);
            that.RenderTransform = tg;
        }

        /// <summary>
        /// Transforms a Framework Element to the given absolution position, relative to the given UIElement
        /// (usually Application.Current.RootVisual).
        /// </summary>
        /// <param name="that"></param>
        /// <param name="position"></param>
        /// <param name="relativeTo">Usually Application.Current.RootVisual, but can be set to any UIElement.</param>
        public static void AbsoluteTransformPositionTo(this FrameworkElement that, Point position, UIElement relativeTo)
        {
            var rv = relativeTo ?? Application.Current.RootVisual;
            if (rv == null) return;

            var gt = that.TransformToVisual(rv);
            var currentLoc = gt.Transform(DefaultPoint);

            that.RelativeTransformPositionTo(new Point(position.X - currentLoc.X, position.Y - currentLoc.Y));
        }

        /// <summary>
        /// Returns the position of a FrameworkElement relative to the given UIElement, or Application.Root.Visual
        /// </summary>
        /// <param name="that"></param>
        /// <param name="relativeTo"></param>
        /// <returns></returns>
        public static Point GetPosition(this FrameworkElement that, UIElement relativeTo)
        {
            var rv = relativeTo ?? Application.Current.RootVisual;

            if (rv == null) return DefaultPoint;

            return that
                    .TransformToVisual(rv)
                    .Transform(DefaultPoint);
        }

        /// <summary>
        /// Finds and returns the first Parent in the visual tree that matches the given Type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="startingElement"></param>
        /// <returns></returns>
        public static T FindParentByType<T>(this DependencyObject startingElement) where T : class
        {
            var p = VisualTreeHelper.GetParent(startingElement);
            if (p == null) return default(T);
            if (p is T) return p as T;
            return p.FindParentByType<T>();
        }

        /// <summary>
        /// Performs a recursive search for an element throughout the entire visual tree, starting from the given element.
        /// </summary>
        /// <param name="startingElement">the element to start searching from</param>
        /// <returns>First Framework Element matching the given Type, or if not found: default(FrameworkElement)</returns>
        public static T FindElement<T>(this DependencyObject startingElement) where T : class
        {
            //if (startingElement is T) return startingElement as T;

            var childCount = VisualTreeHelper.GetChildrenCount(startingElement);
            for (var i = 0; i < childCount; i++)
            {
                var o = VisualTreeHelper.GetChild(startingElement, i);
                if (o is T) return o as T;

                var cr = o.FindElement<T>();
                if (cr != default(T)) return cr;
            }
            return default(T);
        }

        /// <summary>
        /// Performs a recursive search for an element throughout the entire visual tree, starting from the given element
        /// </summary>
        /// <param name="startingElement">the element to start searching from</param>
        /// <param name="name">the x:Name to find</param>
        /// <returns>Framework Element matching both the given name and Type provided in generic declaration (T), or if not found: default(FrameworkElement)</returns>
        public static T FindElement<T>(this DependencyObject startingElement, string name) where T : class
        {
            //if (startingElement is T && startingElement is FrameworkElement && ((FrameworkElement)startingElement).Name == name)
            //    return startingElement as T;

            var childCount = VisualTreeHelper.GetChildrenCount(startingElement);
            for (var i = 0; i < childCount; i++)
            {
                var o = VisualTreeHelper.GetChild(startingElement, i);
                if (o is T && ((FrameworkElement)o).Name == name)
                    return (o as T);

                var cr = o.FindElement<T>(name);
                if (cr != default(T)) return cr;
            }
            return default(T);
        }

        /// <summary>
        /// Returns an IObservable which outputs any FrameworkElements found in entire visual tree of an element, by type
        /// </summary>
        /// <typeparam name="T">FrameworkElement Type to search for</typeparam>
        /// <param name="startingElement">starting element to begin visual tree search</param>
        /// <returns></returns>
        public static IObservable<T> FindAllElementsByType<T>(this DependencyObject startingElement) where T : class
        {
            return Observable.Create<T>(observer =>
            {
                //if (startingElement is T)
                //    observer.OnNext(startingElement as T);

                FindAllElementsByType(startingElement, observer);
                observer.OnCompleted();
                return () => { };
            });
        }

        private static void FindAllElementsByType<T>(DependencyObject element, IObserver<T> observer) where T : class
        {
            var childCount = VisualTreeHelper.GetChildrenCount(element);

            for (var i = 0; i < childCount; i++)
            {
                var o = VisualTreeHelper.GetChild(element, i);
                if (o is T)
                    observer.OnNext(o as T);

                FindAllElementsByType(o, observer);
            }
        }
    }
}
